#include "LayerSetModel.h"

#include "LeGraphicsView/LeGraphicsItem.h"
#include "LeGraphicsView/LeGraphicsItemLayer.h"
#include "LeGraphicsView/LeGraphicsFeatureOption.h"

#include "LeIpc2581/Layer.h"

#include <QDebug>

struct LayerSetItem
{
    LayerSetItem():
        sequenceNumber(-1), enabled(false), ipcLayer(nullptr), graphicsLayer(nullptr)
    {}

    explicit LayerSetItem(int sequenceNumber, const Ipc2581b::Layer *ipcLayer,
                         bool enabled, LeGraphicsItemLayer *graphicsLayer):
        sequenceNumber(sequenceNumber), enabled(enabled), ipcLayer(ipcLayer), graphicsLayer(graphicsLayer)
    {}

    int sequenceNumber;
    bool enabled;
    const Ipc2581b::Layer *ipcLayer;
    LeGraphicsItemLayer *graphicsLayer;
};

inline bool operator==(const LayerSetItem& lhs, const LayerSetItem& rhs)
{
    return lhs.sequenceNumber == rhs.sequenceNumber &&
            lhs.enabled == rhs.enabled && lhs.ipcLayer == rhs.ipcLayer &&
            lhs.graphicsLayer == rhs.graphicsLayer;
}

inline bool operator!=(const LayerSetItem& lhs, const LayerSetItem& rhs)
{
    return !(lhs == rhs);
}


class LayerSetModelPrivate
{
    Q_DISABLE_COPY(LayerSetModelPrivate)
    Q_DECLARE_PUBLIC(LayerSetModel)
    LayerSetModel * const q_ptr;

    explicit LayerSetModelPrivate(LayerSetModel *model):
        q_ptr(model)
    {}

    QList<LayerSetItem> m_data;

    inline void clear()
    {
        m_data.clear();
    }
};

LayerSetModel::LayerSetModel(QObject *parent):
    QAbstractTableModel(parent),
    d_ptr(new LayerSetModelPrivate(this))
{
}

LayerSetModel::~LayerSetModel()
{

}

void LayerSetModel::addLayer(int sequenceNumber, bool enabled, const Ipc2581b::Layer *ipcLayer, LeGraphicsItemLayer *graphicsLayer)
{
    Q_D(LayerSetModel);

    beginInsertRows(QModelIndex(), d->m_data.count(), d->m_data.count());
    d->m_data.append(LayerSetItem(sequenceNumber, ipcLayer, enabled, graphicsLayer));
    endInsertRows();
}

QList<LeGraphicsItemLayer *> LayerSetModel::enabledGraphicsLayers() const
{
    Q_D(const LayerSetModel);
    QList<LeGraphicsItemLayer *> result;

    for (auto item: d->m_data)
        if (item.enabled)
            result.append(item.graphicsLayer);

    return result;
}

int LayerSetModel::layerSequenceNumber(const QString &ipcName) const
{
    Q_D(const LayerSetModel);

    for (auto item: d->m_data)
        if (item.ipcLayer->name == ipcName)
            return item.sequenceNumber;

    return -1;
}

LeGraphicsItemLayer *LayerSetModel::graphicsLayer(const QString &ipcName) const
{
    Q_D(const LayerSetModel);

    for (auto item: d->m_data)
        if (item.ipcLayer->name == ipcName)
            return item.graphicsLayer;

    return nullptr;
}

LeGraphicsItemLayer *LayerSetModel::graphicsLayer(const QModelIndex &index) const
{
    Q_D(const LayerSetModel);

    if (!index.isValid())
        return nullptr;

    return d->m_data.value(index.row()).graphicsLayer;
}

QModelIndex LayerSetModel::moveToTop(const QModelIndex &index)
{
    if (moveRow(QModelIndex(), index.row(), QModelIndex(), 0))
        return createIndex(0, 0);
    return index;
}

QModelIndex LayerSetModel::moveToBottom(const QModelIndex &index)
{
    if (moveRow(QModelIndex(), index.row(), QModelIndex(), rowCount()))
        return createIndex(rowCount() - 1, 0);
    return index;
}

QModelIndex LayerSetModel::moveUp(const QModelIndex &index)
{
    if (moveRow(QModelIndex(), index.row(), QModelIndex(), index.row() - 1))
        return createIndex(index.row() - 1, 0);
    return index;
}

QModelIndex LayerSetModel::moveDown(const QModelIndex &index)
{
    if (moveRow(QModelIndex(), index.row(), QModelIndex(), index.row() + 2))
        return createIndex(index.row() + 1, 0);
    return index;
}

void LayerSetModel::clear()
{
    Q_D(LayerSetModel);

    beginResetModel();
    d->clear();
    endResetModel();
}

QVariant LayerSetModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal)
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    switch (section)
    {
        case EnabledColumn: return "Ena.";
        case VisibleColumn: return "Vis.";
        case SequenceNumberColumn: return "Seq";
        case NameColumn: return "Name";
        case PadVisibleColumn: return "Pad";
        case PadBrushColumn: return "Brush";
        case PinVisibleColumn: return "Pin";
        case PinBrushColumn: return "Brush";
        case FiducialVisibleColumn: return "Fid.";
        case FiducialBrushColumn: return "Brush";
        case HoleVisibleColumn: return "Hole";
        case HoleBrushColumn: return "Brush";
        case SlotVisibleColumn: return "Slot";
        case SlotBrushColumn: return "Brush";
        case FillVisibleColumn: return "Fill";
        case FillBrushColumn: return "Brush";
        case TraceVisibleColumn: return "Trace";
        case TraceBrushColumn: return "Brush";
        case TextVisibleColumn: return "Text";
        case TextBrushColumn: return "Brush";
        default: return "?";
    }
}


int LayerSetModel::rowCount(const QModelIndex &parent) const
{
    Q_D(const LayerSetModel);
    Q_UNUSED(parent);

    return d->m_data.count();
}

int LayerSetModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return ColumnCount;
}

QVariant LayerSetModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    Q_D(const LayerSetModel);

    const LayerSetItem &item = d->m_data.value(index.row());
    const LeGraphicsFeatureOption &option = item.graphicsLayer->featureOption();

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch (index.column())
        {
            case EnabledColumn:
                return item.enabled;
            case VisibleColumn:
                return item.graphicsLayer->isVisible();
            case SequenceNumberColumn:
                return item.sequenceNumber;
            case NameColumn:
                return item.ipcLayer->name;
            case PadVisibleColumn:
                return option.isFeatureVisible(GftPad);
            case PadBrushColumn:
                return option.featureBrush(GftPad);
            case PinVisibleColumn:
                return option.isFeatureVisible(GftPin);
            case PinBrushColumn:
                return option.featureBrush(GftPin);
            case FiducialVisibleColumn:
                return option.isFeatureVisible(GftFiducial);
            case FiducialBrushColumn:
                return option.featureBrush(GftFiducial);
            case HoleVisibleColumn:
                return option.isFeatureVisible(GftHole);
            case HoleBrushColumn:
                return option.featureBrush(GftHole);
            case SlotVisibleColumn:
                return option.isFeatureVisible(GftSlot);
            case SlotBrushColumn:
                return option.featureBrush(GftSlot);
            case FillVisibleColumn:
                return option.isFeatureVisible(GftFill);
            case FillBrushColumn:
                return option.featureBrush(GftFill);
            case TraceVisibleColumn:
                return option.isFeatureVisible(GftTrace);
            case TraceBrushColumn:
                return option.featureBrush(GftTrace);
            case TextVisibleColumn:
                return option.isFeatureVisible(GftText);
            case TextBrushColumn:
                return option.featureBrush(GftText);
            default:
                return QVariant();
        }
    }
    return QVariant();
}

bool LayerSetModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_D(LayerSetModel);

    if (data(index, role) == value)
        return false;

    if (role != Qt::EditRole)
        return false;

    LayerSetItem &item = d->m_data[index.row()];
    LeGraphicsFeatureOption option = item.graphicsLayer->featureOption();

    int section = index.column();
    switch (section)
    {
        case EnabledColumn:
        {
            bool checked = value.toBool();
            item.enabled = checked;
            emit dataChanged(createIndex(index.row(), 0),
                             createIndex(index.row(), ColumnCount-1));
            emit layerEnabledChanged();
            break;
        }
        case VisibleColumn:
        {
            bool checked = value.toBool();
            item.graphicsLayer->setVisible(checked);
            break;
        }
        case PadVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftPad, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), PadBrushColumn),
                             createIndex(index.row(), PadBrushColumn));
            break;
        }
        case PadBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftPad, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case PinVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftPin, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), PinBrushColumn),
                             createIndex(index.row(), PinBrushColumn));
            break;
        }
        case PinBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftPin, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case FiducialVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftFiducial, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), FiducialBrushColumn),
                             createIndex(index.row(), FiducialBrushColumn));
            break;
        }
        case FiducialBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftFiducial, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case HoleVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftHole, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), HoleBrushColumn),
                             createIndex(index.row(), HoleBrushColumn));
            break;
        }
        case HoleBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftHole, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case SlotVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftSlot, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), SlotBrushColumn),
                             createIndex(index.row(), SlotBrushColumn));
            break;
        }
        case SlotBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftSlot, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case FillVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftFill, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), FillBrushColumn),
                             createIndex(index.row(), FillBrushColumn));
            break;
        }
        case FillBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftFill, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case TraceVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftTrace, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), TraceBrushColumn),
                             createIndex(index.row(), TraceBrushColumn));
            break;
        }
        case TraceBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftTrace, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        case TextVisibleColumn:
        {
            bool checked = value.toBool();
            option.setFeatureVisible(GftText, checked);
            item.graphicsLayer->setFeatureOption(option);
            emit dataChanged(createIndex(index.row(), TextBrushColumn),
                             createIndex(index.row(), TextBrushColumn));
            break;
        }
        case TextBrushColumn:
        {
            auto brush = qvariant_cast<QBrush>(value);
            option.setFeatureBrush(GftText, brush);
            item.graphicsLayer->setFeatureOption(option);
            break;
        }
        default:
            return false;
    }

    emit dataChanged(index, index);

    return true;
}

Qt::ItemFlags LayerSetModel::flags(const QModelIndex &index) const
{
    Q_D(const LayerSetModel);

    if (!index.isValid())
        return Qt::NoItemFlags;

    Qt::ItemFlags itemFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;

    const LayerSetItem &item = d->m_data[index.row()];
    const LeGraphicsFeatureOption &option = item.graphicsLayer->featureOption();

    switch (index.column())
    {
        case EnabledColumn:
        case VisibleColumn:
        case PadVisibleColumn:
        case PinVisibleColumn:
        case FiducialVisibleColumn:
        case HoleVisibleColumn:
        case SlotVisibleColumn:
        case FillVisibleColumn:
        case TraceVisibleColumn:
        case TextVisibleColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled);
            break;
        case PadBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftPad));
            break;
        case PinBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftPin));
            break;
        case FiducialBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftFiducial));
            break;
        case HoleBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftHole));
            break;
        case SlotBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftSlot));
            break;
        case FillBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftFill));
            break;
        case TraceBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftTrace));
            break;
        case TextBrushColumn:
            itemFlags.setFlag(Qt::ItemIsEditable);
            itemFlags.setFlag(Qt::ItemIsEnabled, option.isFeatureVisible(GftText));
            break;
    }

    if (!item.enabled && index.column() != EnabledColumn)
        itemFlags.setFlag(Qt::ItemIsEnabled, false);

    return itemFlags;
}

#include "moc_LayerSetModel.cpp"


bool LayerSetModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
                                     const QModelIndex &destinationParent, int destinationChild)
{
    Q_D(LayerSetModel);

    // Table model, parent is always invalid
    QModelIndex parent;
    if (sourceParent.isValid() || destinationParent.isValid())
        return false;

    // More than one row is not supported (yet)
    if (count > 1)
        return false;

    if (sourceRow < 0 || sourceRow >= rowCount())
        return false;

    if (destinationChild < 0 || destinationChild > rowCount())
        return false;

    // Cannot move X above X
    if (destinationChild == sourceRow)
        return false;

    // Cannot move X above X+1
    if (destinationChild == sourceRow + 1)
        return false;

    int destinationRow = destinationChild;

    // move X above X+N+1 = move X to X+N
    if (destinationRow > (sourceRow + 1 ))
    {
        destinationRow--;
    }

    beginMoveRows(parent, sourceRow, sourceRow,
                  parent, destinationChild);
    d->m_data.move(sourceRow, destinationRow);
    endMoveRows();
    emit layerOrderChanged();

    return true;
}
